package net.cloudcodex.client.widgets;

import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * The <code>DecanterPanel</code> is a {@link FlowPanel} which orders child
 * widgets on their weight.<br>
 * The lightest widget is at the top, the heaviest is at the bottom.
 * 
 * @author ADaroussin
 */
public class DecanterPanel extends FlowPanel {
	// NavigableMap is more suitable but is unavailable in GWT 2.3
	private final SortedMap<Integer, Widget> weights = new TreeMap<Integer, Widget>();

	@Override
	public void add(Widget widget) {
		final Integer weight;
		if (this.weights.isEmpty()) {
			weight = 0;
		} else {
			weight = this.weights.lastKey() + 1;
		}
		this.weights.put(weight, widget);
		super.add(widget);
	}

	@Override
	public void insert(Widget widget, int weight) {
		final Widget lastWidget = this.weights.get(weight);
		if (lastWidget == null) {
			// Insert before heavier widget
			final SortedMap<Integer, Widget> heaviers = this.weights.tailMap(weight);
			if (heaviers.isEmpty()) {
				// Insert at the bottom
				super.add(widget);
			} else {
				final Integer key = heaviers.firstKey();
				final Widget heavierWidget = heaviers.get(key);
				final int heavierIndex = super.getWidgetIndex(heavierWidget);
				super.insert(widget, heavierIndex);
			}
		} else {
			// Replace the widget of the same weight
			final int lastIndex = super.getWidgetIndex(lastWidget);
			super.insert(widget, lastIndex);
			super.remove(lastWidget);
		}
		this.weights.put(weight, widget);
	}

	@Override
	public boolean remove(int weight) {
		final Widget widget = this.weights.remove(weight);
		if (widget == null) {
			return false;
		}
		return super.remove(widget);
	}

	@Override
	public boolean remove(Widget widget) {
		this.weights.remove(this.findWeight(widget));
		return super.remove(widget);
	}

	@Override
	public Widget getWidget(int weight) {
		return this.weights.get(weight);
	}

	@Override
	public int getWidgetIndex(Widget widget) {
		final Integer index = this.findWeight(widget);
		return index == null ? -1 : index;
	}

	/**
	 * Change the weight of the specified <code>widget</code>, so the widget is
	 * moved.
	 * 
	 * @param widget
	 *            the widget to be moved
	 * @param toWeight
	 *            the index where the widget is moved
	 * @return <code>true</code> if a widget has been moved
	 */
	public boolean move(Widget widget, int toWeight) {
		final Widget removedWidget = this.weights.get(this.findWeight(widget));
		if (removedWidget == null) {
			return false;
		}
		this.insert(removedWidget, toWeight);
		return true;
	}

	/**
	 * Change the weight of the widget corresponding of the specified
	 * <code>weight</code>, so the widget is moved.
	 * 
	 * @param fromWeight
	 *            the index of the widget to be moved
	 * @param toWeight
	 *            the index where the widget is moved
	 * @return <code>true</code> if a widget has been moved
	 */
	public boolean move(int fromWeight, int toWeight) {
		final Widget widget = this.weights.get(fromWeight);
		if (widget == null) {
			return false;
		}
		this.insert(widget, toWeight);
		return true;
	}

	private Integer findWeight(Widget widget) {
		for (Entry<Integer, Widget> weight : weights.entrySet()) {
			if (widget.equals(weight.getValue())) {
				return weight.getKey();
			}
		}
		return null;
	}
}
